using Godot;
using System;
using System.Collections.Generic;
using Legendofthebrave.Scripts.Characters;

public partial class Player : CharacterBody2D,StateAction
{
    
    private List<State> GroundStates = new List<State> { State.Idle ,State.Run,State.Landing};
    
    private const float RunSpeed = 160.0f;
    private const float FloorAcceleration = RunSpeed / 0.2f;
    private const float AirAcceleration = RunSpeed / 0.1f;
    private const float JumpVelocity = -320.0f;
    private Vector2 WallJumpVelocity = new Vector2(500.0f, -280.0f);
    
    private float defaultGravity;
    private bool isFirstTick = false;
    
    private Node2D Graphics;
    private AnimationPlayer AnimationPlayerNode;
    private Timer CoyoteTimerNode;
    private Timer JumpRequestTimerNode;
    private StateMachine StateMachineNode;
    
    private RayCast2D HandCheckerNode;
    private RayCast2D FootCHeckerNode;
    private bool canMove = true;
    private bool canJump;
    private bool shouldJump;
    private bool wasOnFloor;



    public override void _Ready()
    {
        Graphics = GetNode<Node2D>("Graphics");
        AnimationPlayerNode = GetNode<AnimationPlayer>("AnimationPlayer");
        CoyoteTimerNode = GetNode<Timer>("CoyoteTimer");
        JumpRequestTimerNode = GetNode<Timer>("JumpRequestTimer");
        HandCheckerNode = GetNode<RayCast2D>("Graphics/Cheker/HandChecker");
        FootCHeckerNode = GetNode<RayCast2D>("Graphics/Cheker/FootChecker");
        StateMachineNode = GetNode<StateMachine>("StateMachine");
        defaultGravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle();
           
}
  
   

    public void TickPhysics(State state,double delta)
    {
        switch (state)
        {
            case State.Idle:
                Move(defaultGravity,delta);
                break;
            case State.Run:
                Move(defaultGravity,delta);
                break;
            case State.Jump:
                Move(isFirstTick ? 0.0f : defaultGravity, delta);
                break;
            case State.Fall:
                Move(defaultGravity,delta);
                break;
            case State.Landing:
                Stand(defaultGravity,delta);
                break;
            case State.WallSliding:
                Move(defaultGravity / 3,delta);
                Graphics.Scale = new(GetWallNormal().X, Graphics.Scale.Y);
                break;
            case State.WallJump:
                if (StateMachineNode.StateTime < 0.1)
                {
                    Stand(isFirstTick ? 0.0f : defaultGravity, delta);
                    Graphics.Scale = new(GetWallNormal().X, Graphics.Scale.Y);
                }
                else
                {
                    Move(defaultGravity, delta);
                }
                
                break;
        }

        isFirstTick = false;
    }

    private void Move( float gravity,double delta)
    {
        var direction = Input.GetAxis("MoveLeft", "MoveRight");
        var acceleration = IsOnFloor() ? FloorAcceleration : AirAcceleration;
        Velocity = new(Mathf.MoveToward(Velocity.X, direction * RunSpeed, acceleration * (float)delta),Velocity.Y + gravity * (float)delta);

        if (!Mathf.IsZeroApprox(direction))
            Graphics.Scale = new Vector2(direction < 0 ? -1 : +1 ,Graphics.Scale.Y);
        MoveAndSlide();
    }

    private void Stand(float gravity,double delta)
    {
        var direction = Input.GetAxis("MoveLeft", "MoveRight");
        var acceleration = IsOnFloor() ? FloorAcceleration : AirAcceleration;
        Velocity = new(Mathf.MoveToward(Velocity.X,0.0f, acceleration * (float)delta),Velocity.Y + gravity * (float)delta);
        MoveAndSlide();
    }

    private bool CanWallSlide()
    {
        return IsOnWall() && HandCheckerNode.IsColliding() && FootCHeckerNode.IsColliding();
    }
    
    public override void _UnhandledInput(InputEvent @event)
    {
        if(@event.IsActionPressed("Jump"))
            JumpRequestTimerNode.Start();
        if (@event.IsActionReleased("Jump"))
        {
            JumpRequestTimerNode.Stop(); 
            if( Velocity.Y < JumpVelocity / 2) 
                Velocity = new(Velocity.X,JumpVelocity / 2);
        }

    }
    


    public State GetNextState(State state)
    {
        var canJump = IsOnFloor() || CoyoteTimerNode.TimeLeft > 0;
        var shouldJump = canJump && JumpRequestTimerNode.TimeLeft > 0;

        if (shouldJump)
            return State.Jump;
        
        var direction = Input.GetAxis("MoveLeft", "MoveRight");
        var isStill = Mathf.IsZeroApprox(direction) && Mathf.IsZeroApprox(Velocity.X);
        
        switch (state)
        {
            case State.Idle:
                if (!IsOnFloor())
                    return State.Fall;
                if (!isStill)
                    return State.Run;
                break;
            case State.Run:
                if (!IsOnFloor())
                    return State.Fall;
                if (isStill)
                    return State.Idle;
                break;
            case State.Jump:
                if (Velocity.Y >= 0)
                    return State.Fall;
                break;
            case State.Fall:
                if (IsOnFloor())
                    return isStill ? State.Landing : State.Run;
                if (CanWallSlide())
                    return State.WallSliding;
                break;
            case State.Landing:
                if (!isStill)
                    return State.Run;
                if (!AnimationPlayerNode.IsPlaying())
                    return State.Idle;
                break;
            case State.WallSliding:
                if (JumpRequestTimerNode.TimeLeft > 0)
                    return State.WallJump;
                if (IsOnFloor())
                    return State.Idle;
                if (!IsOnWall() || !FootCHeckerNode.IsColliding())
                    return State.Fall;
                break;
            case State.WallJump:
                if (CanWallSlide() && !isFirstTick)
                    return State.WallSliding;
                if (Velocity.Y >= 0)
                    return State.Fall; 
                break;
        }
        
        return state;
    }

    public void TransitionState(State from, State to)
    {
        if(!GroundStates.Contains(from) && GroundStates.Contains(to))
            CoyoteTimerNode.Stop();
        
        switch (to)
        {
            case State.Idle:
                AnimationPlayerNode.Play("Idle");
                break;
            case State.Run:
                AnimationPlayerNode.Play("Run");
                break;
            case State.Jump:
                AnimationPlayerNode.Play("Jump");
                Velocity = new(Velocity.X, JumpVelocity);
                CoyoteTimerNode.Stop();
                JumpRequestTimerNode.Stop();
                break;
            case State.Fall:
                AnimationPlayerNode.Play("Fall");
                if(GroundStates.Contains(from))
                    CoyoteTimerNode.Start();
                break;
            case State.Landing:
                AnimationPlayerNode.Play("Landing");
                break;
            case State.WallSliding:
                AnimationPlayerNode.Play("WallSliding");
                break;
            case State.WallJump:
                AnimationPlayerNode.Play("Jump");
                Velocity = new (WallJumpVelocity.X * GetWallNormal().X ,WallJumpVelocity.Y);
                JumpRequestTimerNode.Stop();
                break;
            
        }

        isFirstTick = true;
    }
}

